// DreamSpaceJS System (See dreamspace.codeplex.com)
// By James Wilkins (jameswilkins.net)
// License: LGPL (can be used in commercial works, but changes to the source should be made available and shared freely)
//
//   The DreamSpaceJS system allows you to modularize your JavaScript code to allow your UI to load fast - only the script that is needed at the time is loaded
// and run.  As well, developers that program against the GraphItem tree model of the DreamSpace system can maintain cross-platform (including server-based)
// application code using a single JS code base. This means no DOM is required, so browser inconstancies should not be a factor, and users can run their
// applications in any pure JavaScript environment (such as a JS based server, like DreamSpace.NET [using V8.NET]).
//   Developing with DSJS is akin to developing the "Model" and "Controller" part of an MVC design methodology.  The assumption is that without the view
// (or UI), the application itself can still function as a whole (you just can't see it, or interact with it).  This allows the UI to be integrated in any
// form desired.  This also allows the application to run on a server, and have clients "sync" with it - which can promote real-time multi-user networking
// environments for client - server communications, such as when using sockets or WebRTC (eg. game development).   In fact, the "graph" system is directly
// taken from game development concepts.  In short, DSJS allows you to create your application shell, with piece of mind that it will always be relevant for
// any future system (as long as you adhere to the DSJS core principles: If it's not in DSJS, or you don't have fallbacks in case a feature it missing, then
// you may break your app).
//    "That's nice, but what about the 'view' part of MVC? What about our poor UI designers?"  Glad you asked, that's no problem.  You can either use the DSJS
// graph system to build your app (the HTML source being somewhat hidden), or design your page body and have DSJS "import" it as a template. :)  When importing
// as a template, all the element IDs and attributes will be available for the scripts.
//
// The DreamSpace.ts/.js file holds the basic minimum loader and logging routines for the whole DreamSpaceJS system.  Only the parts actually needed get loaded
// on demand, which promotes faster page rendering.  This allows the site users to see pages much sooner, rather than experiencing lag while all scripts (used
// or not) are loaded and compiled.
//
// Good Performance Rules: http://developer.yahoo.com/performance/rules.html

// ===========================================================================================================================
// Setup host bridge, etc., if applicable.

// ... if there's no DOM, then the required minimal DOM informational parts need to be simulated ...

if (typeof window == "undefined")
    window = <Window>this;

if (typeof HTMLElement == "undefined")
    HTMLElement = <any>(function () { });

if (typeof document == "undefined")
    document = <Document>{
        title: "",
        location: { href: "" },
        getElementById: (elementId: string): HTMLElement => { return null; },
        getElementsByTagName: (tagname: string): NodeList => { return <NodeList><any>[]; },
        createElement: (tagName: string): HTMLElement => { return null; }
    };

// =======================================================================================================================
// Basic Window hooks 

if (typeof window !== 'undefined') {
    // If a window error event callback is available, hook into it to provide some visual feedback in case of errors.
    // (Note: Supports the Bootstrap UI, though it may not be available if an error occurs too early)

    window.onerror = function (eventOrMessage: any, source: string, fileno: number): any {
        // ... create a log entry of this first ...
        DreamSpace.System.Diagnostics.log("window.onerror", eventOrMessage + " in '" + source + "' on line " + fileno + ".", DreamSpace.System.Diagnostics.LogTypes.Error);
        // ... make sure the body is visible ...
        document.body.style.display = ""; // (show the body in case it's hidden)
        // ... format the error ...
        if (typeof eventOrMessage !== 'string') eventOrMessage = "" + eventOrMessage;
        var msgElement = document.createElement("div");
        msgElement.innerHTML = "<button type='button' class='close' data-dismiss='alert'>&times;</button><strong>"
        + (<string>eventOrMessage).replace(/\r\n/g, "<br/>\r\n") + "<br/>\r\nError source: '" + source + "' on line " + fileno + "<br/>\r\n</strong>\r\n";
        msgElement.className = "alert alert-danger";
        document.body.appendChild(msgElement);
    };

    // Add a simple keyboard hook to display debug information.
    document.onkeypress = document.onkeydown = function (e: Event) {
        var keyCode;
        var evt: any = e ? e : window.event;
        if (evt.type == "keydown") {
            keyCode = evt.keyCode;
        }
        else {
            keyCode = evt.charCode ? evt.charCode : evt.keyCode;
        }
        if (keyCode == 192 && evt.ctrlKey && DreamSpace.System.Diagnostics.debug) { // (CTRL+~) key
            var body = document.getElementById("main");
            if (body)
                body.style.display = ""; // (show the main element if hidden)
            var headerDiv = document.createElement("h1");
            headerDiv.innerHTML = "<h1><a name='__dslog__' id='__dslog__'>DreamSpace Log:</a></h1>\r\n";
            var div = document.createElement("div");
            div.innerHTML = DreamSpace.System.Diagnostics.getLogAsHTML();
            document.body.appendChild(headerDiv);
            document.body.appendChild(div);
            headerDiv.onclick = () => { alert("DreamSpace Log: \r\n" + DreamSpace.System.Diagnostics.getLogAsText()); };
            location.hash = "#__dslog__";
        }
    }
}

// =======================================================================================================================

/** The "fake" browser host object is only used when there is no .NET host available.
* In this case, the environment is running as a simple web application.
*/
class __NonDreamSpaceHost__ implements IHostBridge {

    constructor() { }

    getCurrentDir(): string { return document.location.href; }

    isStudio(): boolean { return false; }
    isServer(): boolean { return false; }
    isClient(): boolean { return !this.isServer() && !this.isStudio(); }

    setTitle(title: string) { document.title = title; }
    getTitle(): string { return document.title; }

    isDebugMode(): boolean { return false; }
}

var $ICE = null;
// TODO: $ICE loads as a module, and should do this differently.
//??else
//??    $ICE = <IHostBridge_ICE>host;

// =======================================================================================================================
// If the host is in debug mode, then this script should try to wait on it.
// Note: This many only work if the debugger is actually open when this script executes.

if (typeof host === 'object' && host.isDebugMode && host.isDebugMode())
    debugger;

// ===========================================================================================================================

// DreamSpace.baseModulePath = "{YOUR CUSTOM PATH HERE}"; // Note: This already defaults to 'DreamSpaceJS/' internally.

/** The root namespace, which contains the DreamSpace top level scope namespaces and types (instead of using the host's global scope). */
module DreamSpace {

    export declare module NativeTypes {
        export interface IFunction extends Function { }
        export interface IObject extends Object { }
        export interface IArray<T> extends Array<T> { }
        export interface IString extends String { }
        export interface INumber extends Number { }
        export interface IBoolean extends Boolean { }
        export interface IRegExp extends RegExp { }
        export interface IDate extends Date { }
        export interface IIMath extends Math { }
        export interface IError extends Error { }
        export interface IXMLHttpRequest extends XMLHttpRequest { }
        export interface IHTMLElement extends HTMLElement { }
    }

    export declare module NativeStaticTypes {
        export var StaticFunction: typeof Function;
        export var StaticObject: typeof Object;
        export var StaticArray: typeof Array;
        export var StaticString: typeof String;
        export var StaticNumber: typeof Number;
        export var StaticBoolean: typeof Boolean;
        export var StaticRegExp: typeof RegExp;
        export var StaticDate: typeof Date;
        export var StaticMath: typeof Math;
        export var StaticError: typeof Error;
        export var StaticXMLHttpRequest: typeof XMLHttpRequest;
        export var StaticHTMLElement: typeof HTMLElement;
    }

    export interface IStaticGlobals extends Window {
        Function: typeof NativeStaticTypes.StaticFunction;
        Object: typeof NativeStaticTypes.StaticObject;
        Array: typeof NativeStaticTypes.StaticArray;
        String: typeof NativeStaticTypes.StaticString;
        Number: typeof NativeStaticTypes.StaticNumber;
        Boolean: typeof NativeStaticTypes.StaticBoolean;
        RegExp: typeof NativeStaticTypes.StaticRegExp;
        Date: typeof NativeStaticTypes.StaticDate;
        Math: typeof NativeStaticTypes.StaticMath;
        Error: typeof NativeStaticTypes.StaticError;
        XMLHttpRequest: typeof NativeStaticTypes.StaticXMLHttpRequest;
        HTMLElement: typeof NativeStaticTypes.StaticHTMLElement;
    }

    // =======================================================================================================================

    export var UNDEFINED = void (0);

    /** A reference to the host's global environment (convenient for nested TypeScript code, or when using strict mode [where this=undefined]).
    * This provides a faster, cleaner, consistent, and reliable method of referencing the global environment scope without having to resort to workarounds.
    */
    export var global: IStaticGlobals = (function () { return function () { }.constructor("return this"); })(); // (note: this is named as 'global' to support the NodeJS "global" object as well [for compatibility, or to ease portability])

    /**
     * An empty object whose sole purpose is to store global properties by plugin namespace. It exists as an alternative to using the global
     * JavaScript host environment, but also supports it as well.  The get/set methods always use index based lookups, so no string
     * concatenation is used, which makes the process many times faster. 
     * Note: It's never a good idea to put anything in the global HOST scope, as various frameworks and plugins might set conflicting
     * property names.  To be safe, make sure to always use the 'DreamSpace.globals.register()' function.  It can create isolated 
     * global variables, and if necessary, also create a safer unique HOST global scope name.
     */
    export module globals { //http://jsperf.com/string-concat-vs-nested-object-lookups
        var _namespaces: { [index: string]: string } = {};
        var _nsCount: number = 1;

        /**
        * Registers and initializes a global property for the specified plugin, and returns the dot-delimited string reference (see DreamSpace.globals).
        * @param {Plugins.Plugin.$Type} plugin  The plugin associated with the 
        * @param {T} initialValue  The initial value to set.
        * @param {boolean} asHostGlobal  If true, a host global scope unique variable name is returned. If false (default), a dot-delimited one is returned
        *                                instead which references the global variable within the DreamSpace namespace related global scope (so as not to
        *                                pollute the host's global scope).
        *                                Some frameworks, such as the Google Maps API, support callbacks with dot-delimited names for nested objects to help
        *                                prevent global scope pollution.
        */
        export function register<T>(plugin: Scripts.Modules.Plugin.$Type, name: string, initialValue: T, asHostGlobal?: boolean): string;
        /**
        * Registers and initializes a global property for the specified namespace, and returns the dot-delimited string reference (see DreamSpace.globals).
        * @param {string} namespace  Any string that is unique to your application/framework/plugin/etc. (usually a URI of some sort), and is used to group globals
        *                            under a single object scope to prevent naming conflicts.  When plugins are used, the relative path is used as the namespace.
        *                            A windows-style GUID is perfectly fine as well.
        * @param {T} initialValue  The initial value to set.
        * @param {boolean} asHostGlobal  If true, a host global scope unique variable name is returned. If false (default), a dot-delimited one is returned
        *                                instead which references the global variable within the DreamSpace namespace related global scope (so as not to
        *                                pollute the host's global scope).
        *                                Some frameworks, such as the Google Maps API, support callbacks with dot-delimited names for nested objects to help
        *                                prevent global scope pollution.
        */
        export function register<T>(namespace: string, name: string, initialValue: T, asHostGlobal?: boolean): string;
        export function register<T>(namespace: any, name: string, initialValue: T, asHostGlobal: boolean = false): string {
            var nsID: string, nsglobals: { [index: string]: any };
            if (typeof namespace == 'object' && namespace instanceof Plugins.Plugin.$Type)
                namespace = (<Plugins.Plugin.$Type>namespace).path;
            if (!(namespace in _namespaces))
                _namespaces[namespace] = nsID = '_' + _nsCount++;
            else
                nsID = _namespaces[namespace];
            nsglobals = globals[nsID];
            if (!nsglobals)
                globals[nsID] = nsglobals = {};
            if (name in nsglobals)
                throw System.Exception.from("The global variable name '" + name + "' already exists in the global namespace '" + namespace + "'.", namespace);
            if (asHostGlobal) {
                var hostGlobalName = "DreamSpace_" + nsID + "_" + name;
                nsglobals[name] = { "global": global, "hostGlobalName": hostGlobalName };// (any namespace global value referencing the global [window] scope is a redirect to lookup the value name there instead)
                global[hostGlobalName] = initialValue;
                return hostGlobalName;
            } else {
                nsglobals[name] = initialValue;
                if (/^[A-Z_\$]+[A-Z0-9_\$]*$/gim.test(name))
                    return "DreamSpace." + nsID + "." + name;
                else
                    return "DreamSpace." + nsID + "['" + name.replace(/'/g, "\\'") + "']";
            }
        };

        /**
        * Returns true if the specified global variable name is registered.
        */
        export function exists<T>(plugin: Plugins.Plugin.$Type, name: string): boolean {
            var namespace = plugin.path, nsID: string, nsglobals: { [index: string]: any };
            if (!(namespace in globals))
                return false;
            nsID = _namespaces[namespace];
            nsglobals = globals[nsID];
            if (!(name in nsglobals))
                return false;
            return true;
        };

        /**
        * Erases the registered global variable (by setting it to 'undefined' - which is faster than deleting it).
        */
        export function erase<T>(plugin: Plugins.Plugin.$Type, name: string): boolean {
            var namespace = plugin.path, nsID: string, nsglobals: { [index: string]: any };
            if (!(namespace in globals))
                return false;
            nsID = _namespaces[namespace];
            nsglobals = globals[nsID];
            if (!(name in nsglobals))
                return false;
            var existingValue = nsglobals[name];
            if (existingValue && existingValue["global"] == global)
                delete global[existingValue["hostGlobalName"]];
            return nsglobals[name] = void 0;
        };

        /**
        * Clears all registered globals by releasing the associated global object for the specified plugin's namespace
        * and creating a new object.  Any host globals are deleted first.
        * Return true on success, and false if the namespace doesn't exist.
        */
        export function clear<T>(plugin: Plugins.Plugin.$Type): boolean {
            var namespace = plugin.path, nsID: string, nsglobals: { [index: string]: any };
            if (!(namespace in globals))
                return false;
            nsID = _namespaces[namespace];
            nsglobals = globals[nsID];
            for (name in nsglobals) {
                var existingValue = nsglobals[name];
                if (existingValue && existingValue["global"] == global)
                    delete global[existingValue["hostGlobalName"]];
            }
            globals[nsID] = {};
            return true;
        };

        /**
        * Sets and returns a global property value.
        */
        export function setValue<T>(plugin: Plugins.Plugin.$Type, name: string, value: T): T {
            var namespace = plugin.path, nsID: string, nsglobals: { [index: string]: any };
            if (!(namespace in globals))
                throw System.Exception.from("The namespace '" + namespace + "' does not exist - did you remember to call 'DreamSpace.globals.register()' first?", plugin);
            nsID = _namespaces[namespace];
            nsglobals = globals[nsID];
            if (!(name in nsglobals))
                throw System.Exception.from("The global variable name '" + name + "' was not found in the global namespace '" + namespace + "' - did you remember to call 'DreamSpace.globals.register()' first?", plugin);
            var existingValue = nsglobals[name];
            if (existingValue && existingValue["global"] == global) {
                return global[existingValue["hostGlobalName"]] = value;
            }
            else return nsglobals[name] = value;
        };

        /**
        * Gets a global property value.
        */
        export function getValue<T>(plugin: Plugins.Plugin.$Type, name: string): T {
            var namespace = plugin.path, nsID: string, nsglobals: { [index: string]: any };
            if (!(namespace in globals))
                throw System.Exception.from("The namespace '" + namespace + "' does not exist - did you remember to call 'DreamSpace.globals.register()' first?", plugin);
            nsID = _namespaces[namespace];
            nsglobals = globals[nsID];
            if (!(name in nsglobals))
                return void 0;
            var existingValue = nsglobals[name];
            if (existingValue && existingValue["global"] == global) {
                return global[existingValue["hostGlobalName"]];
            }
            else return nsglobals[name];
        };
    };

    export var __rootNamespace: string = "DreamSpace";

    /** A simple function that does nothing.
    * This is used to clear certain function properties, such as when preventing client/server functions based on platform, or
    * replacing the 'disposal' function on object instances after disposal.
    */
    export function noop(): void { }

    /** Holds a list of JavaScript compatibility flags for the current environment.
    * Compatibility is done using actual object, property, or feature detection, and not through browser detection.
    * If a detection is not here, it may be because a pollyfill (aka 'shim') was implemented instead.
    * If browser detection is needed (perhaps for speed optimizations, or user feedback purposes), see the "DreamSpace.Browser" namespace.
    */
    export module Compatibility {
        /** Set 'true' if the ES5 'Object.defineProperty(obj, prop, descriptor)' function is fully supported for all object
        * types (not just partially like IE8). */
        export var objectDefineProperty: boolean = (function () {
            try { if (Object.defineProperty) return Object.defineProperty({}, '_', { value: null })._ === null; } catch (e) { }
            return false;
        } ());

        /** Set 'true' if the ES5 'Object.create(proto [, propertiesObject ])' function is fully supported.
        * Note: There is a pollyfill in place for this, but the 'propertiesObject' parameter is not supported.
        */
        export var objectCreate: boolean = (function () {
            return ("" + Object.create).indexOf("[native code]") > 0;
        } ());
    }

    export var host: IHostBridge = (() => {
        // ... make sure the host object is valid for at least checking client/server/studio states
        if (typeof host !== 'object' || typeof host.isClient == 'undefined' || typeof host.isServer == 'undefined' || typeof host.isStudio == 'undefined')
            return new __NonDreamSpaceHost__();
        else
            return host; // (running in a valid host (or emulator? ;) )
    })();

    // =======================================================================================================================

    /** The base module for all DreamSpace framework types. */
    export module System {
        // -------------------------------------------------------------------------------------------------------------------

        /** The name of the environment */
        export function environmentName() { return _EnvironmentName; }
        export function setEnvironmentName(name: string) { _EnvironmentName = "" + name; }
        var _EnvironmentName: string = "DreamSpace Client";

        /** Returns true if this script is running in the DreamSpace Studio/Wrapper host. */
        export function isStudio(): boolean { return host.isStudio(); }

        /** 
        * Returns true if 'host' in the global scope represents the DreamSpace Server .NET environment (JavaScript only - no UI).
        * If true, the host bridge can provide direct access to certain methods on the hosting server, such as access to the file system, environment, registry settings, and more.
        */
        export function isServer(): boolean { return host.isServer(); }

        /** Returns true if 'host' in the global scope represents a basic web navigator/browser application (no DreamSpace specific host bridge is available). */
        export function isClient(): boolean { return host.isClient(); }

        /** Set the title on the application host (this is the '<title></title>' section in HTML clients, or the window title for the studio wrapper). */
        export function setTitle(title: string) {
            host.setTitle(title);
        }

        /** Get the current title of the application host (this is the '<title></title>' section in HTML clients, or the window title for the studio wrapper). */
        export function getTitle(): string {
            return host.getTitle();
        }

        // -------------------------------------------------------------------------------------------------------------------

        // Timers are very useful non-UI browser based features, but may not be implemented the same on all systems, so it 's
        // safer to call these instead.

        /** Trigger a one time callback after a given period of time (in ms). A handle is returned that can be used to cancel the timer. */
        export function setTimeout(handler: any, timeout?: any, ...args: any[]): number {
            // (note: can't call "apply" on some IE7 native host objects, such as 'setTimeout')
            args.unshift(timeout);
            args.unshift(handler);
            return Utilities.apply(window.setTimeout, null, args);
        }
        export function clearTimeout(handle: number): void {
            window.clearTimeout(handle);
        }

        /** Trigger a reoccurring callback on a given interval (in ms). A handle is returned that can be used to cancel the timer. */
        export function setInterval(handler: any, internvalTime?: any, ...args: any[]): number {
            // (note: can't call "apply" on some IE7 native host objects, such as 'setTimeout')
            args.unshift(internvalTime);
            args.unshift(handler);
            return Utilities.apply(window.setTimeout, null, args);
        }
        export function clearInterval(handle: number): void {
            window.clearInterval(handle);
        }

        // -------------------------------------------------------------------------------------------------------------------

        /** A container of some common useful utility functions. This is a module to allow other plugins to extend it if desired. */
        export module Utilities {
        }

        // -------------------------------------------------------------------------------------------------------------------

        /** Path/URL based utilities. */
        export module IO.Path {

            // =======================================================================================================================

            /** Appends 'path2' to 'path1', inserting a path separator character if required. */
            export function combineURLPaths(path1: string, path2: string): string {
                // ... add missing URL path separator if not found (BUT, only if the first path is NOT empty!) ...
                if (path1.length > 0 && path1.charAt(path1.length - 1) != '/') {
                    if (path2.length > 0 && path2.charAt(0) != '/')
                        path1 += '/';
                } else if (path2.length > 0 && path2.charAt(0) == '/')
                    path2 = path2.substring(1); // (strip the additional '/', as it already exists on the end of path1)
                return path1 + path2;
            };

            // =======================================================================================================================

            /** Returns true if the specified extension is missing from the end of 'pathToFile'.
            * An exception is made if special characters are detected (such as "?" or "#"), in which case true is always returned, as the resource may be returned
            * indirectly via a server side script, or handled in some other special way).
            * @param {string} ext The extension to check for (with or without the preceding period [with preferred]).
            */
            export function hasFileExt(pathToFile: string, ext: string): boolean {
                if (ext.length > 0 && ext.charAt(0) != '.') ext = '.' + ext;
                return pathToFile.lastIndexOf(ext) == pathToFile.length - ext.length || pathToFile.indexOf("?") >= 0 || pathToFile.indexOf("#") >= 0;
            }

            // =======================================================================================================================

            export var QUERY_STRING_REGEX: RegExp = /[?|&][a-zA-Z0-9-._]+(?:=[^&#$]*)?/gi;

            /** Helps wrap common functionality for query/search string manipulation.  An internal 'values' object stores the 'name:value'
            * pairs from a URI or 'location.search' string, and converting the object to a string results in a proper query/search string
            * with all values escaped and ready to be appended to a URI. */
            export class Query {

                // -------------------------------------------------------------------------------------------------------------------

                values: { [index: string]: string } = {};

                // -------------------------------------------------------------------------------------------------------------------

                /** Helps to build an object of 'name:value' pairs from a URI or 'location.search' string.
                * @param {string} searchString A URI or 'location.search' string.
                * @param {boolean} makeNamesLowercase If true, then all query names are made lower case when parsing (the default is false).
                */
                constructor(searchString: string = null, makeNamesLowercase: boolean = false) {
                    if (searchString) {
                        var nameValuePairs = searchString.match(QUERY_STRING_REGEX);
                        var i: number, n: number, eqIndex: number, nameValue: string;
                        if (nameValuePairs)
                            for (var i = 0, n = nameValuePairs.length; i < n; ++i) {
                                nameValue = nameValuePairs[i];
                                eqIndex = nameValue.indexOf('='); // (need to get first instance of the '=' char)
                                if (eqIndex == -1) eqIndex = nameValue.length; // (whole string is the name)
                                if (makeNamesLowercase)
                                    this.values[decodeURIComponent(nameValue).substring(1, eqIndex).toLowerCase()] = decodeURIComponent(nameValue.substring(eqIndex + 1)); // (note: the RegEx match always includes a delimiter)
                                else
                                    this.values[decodeURIComponent(nameValue).substring(1, eqIndex)] = decodeURIComponent(nameValue.substring(eqIndex + 1)); // (note: the RegEx match always includes a delimiter)
                            }
                    }
                }

                // -------------------------------------------------------------------------------------------------------------------

                /** Use to add additional query string values. The function returns the current object to allow chaining calls.
                * Example: add({'name1':'value1', 'name2':'value2', ...});
                * Note: Use this to also change existing values.
                */
                addOrUpdate(newValues: { [index: string]: string }): Query {
                    if (newValues)
                        for (var pname in newValues)
                            this.values[pname] = newValues[pname];
                    return this;
                }

                // -------------------------------------------------------------------------------------------------------------------

                /** Use to rename a series of query parameter names.  The function returns the current object to allow chaining calls.
                * Example: rename({'oldName':'newName', 'oldname2':'newName2', ...});
                * Warning: If the new name already exists, it will be replaced.
                */
                rename(newNames: { [index: string]: string }): Query {
                    for (var pname in this.values)
                        for (var pexistingname in newNames)
                            if (pexistingname == pname && newNames[pexistingname] && newNames[pexistingname] != pname) { // (&& make sure the new name is actually different)
                                this.values[newNames[pexistingname]] = this.values[pexistingname];
                                delete this.values[pexistingname];
                            }
                    return this;
                }

                // -------------------------------------------------------------------------------------------------------------------

                /** Use to remove a series of query parameter names.  The function returns the current object to allow chaining calls.
                * Example: remove(['name1', 'name2', 'name3']);
                */
                remove(namesToDelete: string[]): Query {
                    if (namesToDelete && namesToDelete.length)
                        for (var i = 0, n = namesToDelete.length; i < n; ++i)
                            if (this.values[namesToDelete[i]])
                                delete this.values[namesToDelete[i]];
                    return this;
                }

                // -------------------------------------------------------------------------------------------------------------------

                /** Creates and returns a duplicate of this object. */
                clone(): Query {
                    var q = new Query();
                    for (var pname in this.values)
                        q.values[pname] = this.values[pname];
                    return q;
                }

                // -------------------------------------------------------------------------------------------------------------------

                appendTo(uri: string): string {
                    return uri.match(/^[^\?]*/g)[0] + this.toString();
                }

                // -------------------------------------------------------------------------------------------------------------------

                /** Returns the specified value, or a default value if nothing was found. */
                getValue(name: string, defaultValueIfUndefined?: string): any {
                    var value = this.values[name];
                    if (value === void 0) value = defaultValueIfUndefined;
                    return value;
                }

                /** Returns the specified value as a lowercase string, or a default value (also made lowercase) if nothing was found. */
                getLCValue(name: string, defaultValueIfUndefined?: string): string {
                    var value = this.values[name];
                    if (value === void 0) value = defaultValueIfUndefined;
                    return ("" + value).toLowerCase();
                }

                /** Returns the specified value as an uppercase string, or a default value (also made uppercase) if nothing was found. */
                getUCValue(name: string, defaultValueIfUndefined?: string): string {
                    var value = this.values[name];
                    if (value === void 0) value = defaultValueIfUndefined;
                    return ("" + value).toUpperCase();
                }

                /** Returns the specified value as an uppercase string, or a default value (also made uppercase) if nothing was found. */
                getNumber(name: string, defaultValueIfUndefined?: number): number {
                    var value = parseFloat(this.values[name]);
                    if (value === void 0) value = defaultValueIfUndefined;
                    return value;
                }

                // -------------------------------------------------------------------------------------------------------------------

                /** Obfuscates the specified query value (to make it harder for end users to read naturally).  This is done using Base64 encoding.
                * The existing value is replaced by the encoded value, and the encoded value is returned.
                * Note: This is NOT encryption.  It is meant solely as a means to transmit values that may contain characters not supported for URI query values.
                */
                encodeValue(name: string): string {
                    var value = this.values[name], result: string;
                    if (value !== void 0 && value !== null) {
                        this.values[name] = result = Text.Encoding.base64Encode(value, Text.Encoding.Base64Modes.URI);
                    }
                    return result;
                }

                /** Deobfuscates the specified query value (to make it harder for end users to read naturally).  This expects Base64 encoding.
                * The existing value is replaced by the decoded value, and the decoded value is returned.
                */
                decodeValue(name: string): string {
                    var value = this.values[name], result: string;
                    if (value !== void 0 && value !== null) {
                        this.values[name] = result = Text.Encoding.base64Decode(value, Text.Encoding.Base64Modes.URI);
                    }
                    return result;
                }

                /** Encode ALL query values (see 'encodeValue()'). 
                * Note: This is NOT encryption.  It is meant solely as a means to transmit values that may contain characters not supported for URI query values.
                */
                encodeAll(): void {
                    for (var p in this.values)
                        this.encodeValue(p);
                }

                /** Decode ALL query values (see 'encodeValue()'). 
                * Note: This is NOT encryption.  It is meant solely as a means to transmit values that may contain characters not supported for URI query values.
                */
                decodeAll(): void {
                    for (var p in this.values)
                        this.decodeValue(p);
                }

                // -------------------------------------------------------------------------------------------------------------------

                /** Converts the underlying query values to a proper search string that can be appended to a URI. */
                toString(): string {
                    var qstr = "";
                    for (var pname in this.values)
                        if (this.values[pname] !== void 0)
                            qstr += (qstr ? "&" : "?") + encodeURIComponent(pname) + "=" + encodeURIComponent(this.values[pname]);
                    return qstr;
                }

                // -------------------------------------------------------------------------------------------------------------------
            }

            // =======================================================================================================================
        }

        // -------------------------------------------------------------------------------------------------------------------

        /** Contains diagnostic based functions, such as those needed for logging purposes. */
        export module Diagnostics {
            export var __logItems: LogItem[] = [];
            var __logItemsSequenceCounter = 0;
            var __logCaptureStack: LogItem[] = [];

            export enum DebugModes {
                /** Run in release mode, which loads all minified scripts, and calls $DSLoader.run() automatically when all modules have loaded. */
                Release,
                /** Run in debug mode, which loads all un-minified scripts, and calls $DSLoader.run() automatically when all modules have loaded. */
                Debug_Run,
                /** Run in debug mode, which loads all un-minified scripts, and DOES NOT call $DSLoader.run() automatically. */
                Debug_Wait
            }

            /** Sets the debug mode. A developer should set this to one of the desired 'DebugModes' values.
            */
            export var debug: DebugModes = DebugModes.Debug_Run;

            export enum LogTypes {
                /** An important or critical action was a success. */
                Success = -2,
                /** An important action has started, or important information must be noted. */
                Info = -1,
                /** General logging information - nothing of great importance. */
                Normal,
                /** A warning or non critical error has occurred. */
                Warning,
                /** A error has occurred (usually critical). */
                Error
            }

            export class LogItem {
                /** The parent log item. */
                parent: LogItem = null;
                /** The sequence count of this log item. */
                sequence: number = __logItemsSequenceCounter++; // (to maintain correct ordering, as time is not reliable if log items are added too fast)
                /** The title of this log item. */
                title: string;
                /** The message of this log item. */
                message: string;
                /** The time of this log item. */
                time: number;
                /** The type of this log item. */
                type: LogTypes;
                /** The source of the reason for this log item, if any. */
                source: {};

                subItems: LogItem[];

                marginIndex: number = void 0;

                constructor(parent: LogItem, title: string, message: string, type?: LogTypes);
                constructor(parent: LogItem, title: any, message: any, type: LogTypes = LogTypes.Normal) {
                    if (title === void 0 || title === null) {
                        if (message === void 0 || message === null)
                            throw System.Exception.from("LogItem(): A message is required if no title is given.", this);
                        title = "";
                    }
                    if (message === void 0 || message === null) message = "";

                    this.parent = parent;
                    this.title = title.toString && title.toString() || title.toValue && title.toValue() || "" + title;
                    this.message = message.toString && message.toString() || message.toValue && message.toValue() || "" + message;
                    this.time = Date.now(); /*ms*/
                    this.type = type;

                    if (console) { // (if the console object is supported, then write to it)
                        var time = TimeSpan.utcTimeToLocalTime(this.time), margin = "";
                        while (parent) { parent = parent.parent; margin += "  "; }
                        var consoleText = time.hours + ":" + (time.minutes < 10 ? "0" + time.minutes : "" + time.minutes) + ":" + (time.seconds < 10 ? "0" + time.seconds : "" + time.seconds)
                            + " " + margin + this.title + ": " + this.message;
                        switch (type) {
                            case LogTypes.Normal: console.log(consoleText); break;
                            case LogTypes.Info: console.info(consoleText); break;
                            case LogTypes.Warning: console.warn(consoleText); break;
                            case LogTypes.Error: console.error(consoleText); break;
                            case LogTypes.Success: console.info(consoleText); break;
                        }
                    }
                }

                /** Write a message to the log. */
                write(message: string, type?: LogTypes): LogItem;
                /** Write a message to the log. */
                write(message: any, type?: LogTypes): LogItem;
                write(message: any, type: LogTypes = LogTypes.Normal): LogItem {
                    var logItem = new LogItem(this, null, message, type);
                    if (!this.subItems)
                        this.subItems = [];
                    this.subItems.push(logItem);
                    return logItem;
                }

                /** Starts a new sub-log. */
                log(title: string, message: string, type?: LogTypes): LogItem;
                /** Starts a new sub-log. */
                log(title: any, message: any, type?: LogTypes): LogItem;
                log(title: any, message: any, type: LogTypes = LogTypes.Normal): LogItem {
                    var logItem = new LogItem(this, title, message, type);
                    if (!this.subItems)
                        this.subItems = [];
                    this.subItems.push(logItem);
                    return logItem;
                }

                /** Causes all future log writes to be attached to last sub-log item of this log entry.
                * This is usually called at the start of the log for a block of code, where function calls may also trigger log writes. Don't forget to call 'endCapture()' when done.
                * The current instance is returned to allow chaining function calls.
                */
                beginCapture(): LogItem {
                    if (__logCaptureStack.indexOf(this) < 0)
                        __logCaptureStack.push(this);
                    return this;
                }

                /** Causes all future log writes to be attached to last sub item of this log entry.
                * See 'beginCapture()' for more details. */
                endCapture() {
                    var i = __logCaptureStack.lastIndexOf(this);
                    if (i >= 0) __logCaptureStack.splice(i, 1);
                }
            }

            /** Starts a new log entry. */
            export function log(title: string, message: string, type?: LogTypes): LogItem;
            /** Starts a new log entry. */
            export function log(title: any, message: any, type?: LogTypes): LogItem;
            export function log(title: any, message: any, type: LogTypes = LogTypes.Normal): LogItem {
                if (__logCaptureStack.length) {
                    var capturedLogItem = __logCaptureStack[__logCaptureStack.length - 1];
                    var lastLogEntry = capturedLogItem.subItems && capturedLogItem.subItems.length && capturedLogItem.subItems[capturedLogItem.subItems.length - 1];
                    if (lastLogEntry)
                        return lastLogEntry.log(title, message, type);
                    else
                        return capturedLogItem.log(title, message, type); //capturedLogItem.log("", "");
                }
                var logItem = new LogItem(null, title, message, type);
                __logItems.push(logItem);
                return logItem;
            }

            export function getLogAsHTML(): string {
                var i: number, n: number;
                var orderedLogItems: LogItem[] = [];
                var item: LogItem;
                var logHTML = "<div>\r\n", cssClass = "", title: string, icon: string,
                    rowHTML: string, titleHTML: string, messageHTML: string, marginHTML: string = "";
                var logItem: LogItem, lookAheadLogItem: LogItem;
                var time: TimeSpan;
                var cssAndIcon: { cssClass: string; icon: string; };
                var margins: string[] = [""];
                var currentMarginIndex: number = 0;

                function cssAndIconFromLogType_text(type: LogTypes): { cssClass: string; icon: string; } {
                    var cssClass, icon;
                    switch (type) {
                        case LogTypes.Success: cssClass = "text-success"; icon = "&#x221A;"; break;
                        case LogTypes.Info: cssClass = "text-info"; icon = "&#x263C;"; break;
                        case LogTypes.Warning: cssClass = "text-warning"; icon = "&#x25B2;"; break;
                        case LogTypes.Error: cssClass = "text-danger"; icon = "<b>(!)</b>"; break;
                        default: cssClass = ""; icon = "";
                    }
                    return { cssClass: cssClass, icon: icon };
                }
                function reorganizeEventsBySequence(logItems: LogItem[]) {
                    var i: number, n: number;
                    for (i = 0, n = logItems.length; i < n; ++i) {
                        logItem = logItems[i];
                        logItem.marginIndex = void 0;
                        orderedLogItems[logItem.sequence] = logItem;
                        if (logItem.subItems && logItem.subItems.length)
                            reorganizeEventsBySequence(logItem.subItems);
                    }
                }
                function setMarginIndexes(logItem: LogItem, marginIndex: number = 0) {
                    var i: number, n: number;

                    if (marginIndex && !margins[marginIndex])
                        margins[marginIndex] = margins[marginIndex - 1] + "&nbsp;&nbsp;&nbsp;&nbsp;";

                    logItem.marginIndex = marginIndex;

                    // ... reserve the margins needed for the child items ...
                    if (logItem.subItems && logItem.subItems.length) {
                        for (i = 0, n = logItem.subItems.length; i < n; ++i)
                            setMarginIndexes(logItem.subItems[i], marginIndex + 1);
                    }
                }

                // ... reorganize the events by sequence ...

                reorganizeEventsBySequence(__logItems);

                // ... format the log ...

                for (i = 0, n = orderedLogItems.length; i < n; ++i) {
                    logItem = orderedLogItems[i];
                    if (!logItem) continue;
                    rowHTML = "";

                    if (logItem.marginIndex === void 0)
                        setMarginIndexes(logItem);

                    marginHTML = margins[logItem.marginIndex];

                    cssAndIcon = cssAndIconFromLogType_text(logItem.type);
                    if (cssAndIcon.icon) cssAndIcon.icon += "&nbsp;";
                    if (cssAndIcon.cssClass)
                        messageHTML = cssAndIcon.icon + "<strong>" + String.replace(logItem.message, "\r\n", "<br />\r\n") + "</strong>";
                    else
                        messageHTML = cssAndIcon.icon + logItem.message;

                    if (logItem.title)
                        titleHTML = logItem.title + ": ";
                    else
                        titleHTML = "";

                    time = TimeSpan.utcTimeToLocalTime(logItem.time);

                    rowHTML = "<div class='" + cssAndIcon.cssClass + "'>"
                    + time.hours + ":" + (time.minutes < 10 ? "0" + time.minutes : "" + time.minutes) + ":" + (time.seconds < 10 ? "0" + time.seconds : "" + time.seconds) + "&nbsp;"
                    + marginHTML + titleHTML + messageHTML + "</div>" + rowHTML + "\r\n";

                    logHTML += rowHTML + "</ br>\r\n";
                }

                logHTML += "</div>\r\n";

                return logHTML;
            }

            export function getLogAsText(): string {
                //??var logText = "";
                //??for (var i = 0, n = __logItems.length; i < n; ++i)
                //??    logText += String.replaceTags(__logItems[i].title) + ": " + String.replaceTags(__logItems[i].message) + "\r\n";
                return String.replaceTags(String.replace(getLogAsHTML(), "&nbsp;", " "));
            }

        }

        // -------------------------------------------------------------------------------------------------------------------

        /** The Exception object is used to record information about errors that occur in an application.
        * Note: Creating an exception object automatically creates a corresponding log entry, unless the 'log' parameter is set to false.
        */
        export class Exception {

            /** Records information about errors that occur in the application.
            * Note: Creating an exception object automatically creates a corresponding log entry, unless the 'log' parameter is set to false.
            * @param {string} message The error message.
            * @param {object} source An object that is associated with the message, or null.
            * @param {boolean} log True to automatically create a corresponding log entry (default), or false to skip.
            */
            constructor(public message: string, public source: {}, log: boolean = true) {
                if (log) System.Diagnostics.log("Exception", message, System.Diagnostics.LogTypes.Error);
            }

            toString() { return this.message; }
            valueOf() { return this.message; }

            /** Returns the current call stack. */
            static printStackTrace(): string[] { // TODO: Review: http://www.eriwen.com/javascript/stacktrace-update/
                var callstack: string[] = [];
                var isCallstackPopulated = false;
                try {
                    throw "";
                } catch (e) {
                    if (e.stack) { //Firefox
                        var lines = e.stack.split('\n');
                        for (var i = 0, len = lines.length; i < len; ++i) {
                            if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
                                callstack.push(lines[i]);
                            }
                        }
                        //Remove call to printStackTrace()
                        callstack.shift();
                        isCallstackPopulated = true;
                    }
                    else if (window["opera"] && e.message) { //Opera
                        var lines = e.message.split('\n');
                        for (var i = 0, len = lines.length; i < len; ++i) {
                            if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
                                var entry = lines[i];
                                //Append next line also since it has the file info
                                if (lines[i + 1]) {
                                    entry += ' at ' + lines[i + 1];
                                    i++;
                                }
                                callstack.push(entry);
                            }
                        }
                        //Remove call to printStackTrace()
                        callstack.shift();
                        isCallstackPopulated = true;
                    }
                }
                if (!isCallstackPopulated) { //IE and Safari
                    var currentFunction = arguments.callee.caller;
                    while (currentFunction) {
                        var fn = currentFunction.toString();
                        var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
                        callstack.push(fname);
                        currentFunction = currentFunction.caller;
                    }
                }
                return callstack;
            }

            /** A more informative way to generate exception errors.
            * Usage example: "throw System.Exception.from("Error message.", this);"
            * @param {Diagnostics.LogItem} logItem A log item entry to use as the error source.
            * @param {Object} source The object that is the source of the error, or related to it.
            */
            static from(logItem: System.Diagnostics.LogItem, source?: {}): Exception;
            /** A more informative way to generate exception errors.
            * Usage example: "throw System.Exception.from("Error message.", this);"
            * @param {string} message The error message.
            * @param {Object} source The object that is the source of the error, or related to it.
            */
            static from(message: string, source?: {}): Exception;
            static from(message: any, source: {} = null): Exception {
                // (support LogItem objects natively as the exception message source)
                var createLog = true;
                if (typeof message == 'object' && ((<Diagnostics.LogItem>message).title || (<Diagnostics.LogItem>message).message)) {
                    createLog = false;
                    if (source != void 0)
                        (<Diagnostics.LogItem>message).source = source;
                    source = message;
                    message = "";
                    if ((<Diagnostics.LogItem>message).title)
                        message += (<Diagnostics.LogItem>message).title;
                    if ((<Diagnostics.LogItem>message).message) {
                        if (message) message += ": ";
                        message += (<Diagnostics.LogItem>message).message;
                    }
                }
                var callerFunction = System.Exception.from.caller.toString();
                var callerName = DreamSpace.__functions[callerFunction];
                //var srcName = callerFunction.substring(callerFunction.indexOf("function"), callerFunction.indexOf("("));
                var args = System.Exception.from.caller.arguments;
                var _args = args && args.length > 0 ? Array.prototype.join.call(args, ', ') : "";
                var callerSignature = (callerName ? "[" + callerName + "(" + _args + ")]" : "");
                message = (callerSignature ? callerSignature + ": " : "") + message + "\r\n\r\n";
                message += callerFunction + "\r\n\r\nStack:\r\n";
                var caller = System.Exception.from.caller.caller;
                while (caller) {
                    callerFunction = caller.toString();
                    callerName = DreamSpace.__functions[callerFunction];
                    args = caller.arguments;
                    _args = args && args.length > 0 ? Array.prototype.join.call(args, ', ') : "";
                    //srcName = callerFunction.substring(callerFunction.indexOf("function"), callerFunction.indexOf(")") + 1);
                    if (callerName)
                        message += callerName + "(" + _args + ")\r\n";
                    else
                        message += callerFunction + (_args ? " using arguments (" + _args + ")" : "") + "\r\n";
                    caller = caller.caller != caller ? caller.caller : null; // (make sure not to fall into an infinite loop)
                }
                return new Exception(message, source, createLog);
            }

            /** Logs an error and returns an associated 'Exception' object for the caller to throw.
            * The source of the exception object will be the created 'LogItem' object.
            */
            static error(title: string, message: string, source?: {}): Exception {
                var logItem = System.Diagnostics.log(title, message, System.Diagnostics.LogTypes.Error);
                return System.Exception.from(logItem, source);
            }
        }

        // -------------------------------------------------------------------------------------------------------------------

        /** Contains information on the browser being used. */
        export module Browser {
            // (Browser detection is a highly modified version of "http://www.quirksmode.org/js/detect.html".)
            // (Note: This is only required for quirk detection in special circumstances [such as IE's native JSON whitespace parsing issue], and not for object feature support)

            /** A list of browsers that can be currently detected. */
            export enum BrowserTypes {
                /** Browser is not yet detected, or detection failed. */
                Unknown = 0,
                /** Represents a non-browser environment. Any value > 1 is a client side browser. */
                Server = -1,
                IE = 1,
                Chrome,
                FireFox,
                Safari,
                Opera,
                Netscape,
                OmniWeb,
                iCab,
                Konqueror,
                Camino
            }

            /** A list of operating systems that can be currently detected. */
            export enum OperatingSystems {
                /** OS is not yet detected, or detection failed. */
                Unknown = 0,
                Windows,
                Mac,
                Linux,
                iOS
            }

            /** Holds detection parameters for a single browser agent string version.
            * Note: This is agent string related, as the version number itself is pulled dynamically based on 'versionPrefix'.
            */
            export interface BrowserAgentVersionInfo {
                /** The parent 'BrowserInfo' details that owns this object. */
                parent?: BrowserInfo;
                /** The text to search for to select this version entry. If null, use the browser name string. */
                nameTag: string;
                /** The text to search for that immediately precedes the browser version.
                * If null, use the browser name string, appended with '/'. */
                versionPrefix: string;
                /** Used only to override a browser vendor name if a different vendor owned a browser in the past. */
                vendor?: string;
            }

            /** Holds detection parameters for a single browser type, and supported versions. */
            export interface BrowserInfo {
                /** The name of the browser. */
                name: string;
                /** The browser's vendor. */
                vendor: string;
                /** The browser's enum value (see 'Browser.BrowserTypes'). */
                identity: BrowserTypes;
                /** The browser's AGENT STRING versions (see 'Browser.BrowserVersionInfo').
                * Note: This is the most important part, as browser is detected based on it's version details.
                */
                versions: BrowserAgentVersionInfo[];
            }

            /** Holds detection parameters for the host operating system. */
            export interface OSInfo {
                name: string;
                identity: OperatingSystems;
            }

            var __browserList: BrowserInfo[] = (() => {
                var list: BrowserInfo[] = [];
                list[BrowserTypes.Chrome] =
                {
                    name: "Chrome", vendor: "Google", identity: BrowserTypes.Chrome, // (browser details)
                    versions: [{ nameTag: null, versionPrefix: null }] // (list of browser versions; string values default to the browser name if null)
                };
                list[BrowserTypes.OmniWeb] =
                {
                    name: "OmniWeb", vendor: "The Omni Group", identity: BrowserTypes.OmniWeb,
                    versions: [{ nameTag: null, versionPrefix: null }]
                };
                list[BrowserTypes.Safari] =
                {
                    name: "Safari", vendor: "Apple", identity: BrowserTypes.Safari,
                    versions: [{ nameTag: null, versionPrefix: "Version" }]
                };
                list[BrowserTypes.Opera] =
                {
                    name: "Opera", vendor: "Opera Mediaworks", identity: BrowserTypes.Opera,
                    versions: [{ nameTag: null, versionPrefix: "Version" }]
                };
                if ((<any>window).opera) browserVersionInfo = __browserList[BrowserTypes.Opera].versions[0];
                list[BrowserTypes.iCab] =
                {
                    name: "iCab", vendor: "Alexander Clauss", identity: BrowserTypes.iCab,
                    versions: [{ nameTag: null, versionPrefix: null }]
                };
                list[BrowserTypes.Konqueror] =
                {
                    name: "Konqueror", vendor: "KDE e.V.", identity: BrowserTypes.Konqueror,
                    versions: [{ nameTag: "KDE", versionPrefix: "Konqueror" }]
                };
                list[BrowserTypes.FireFox] =
                {
                    name: "Firefox", vendor: "Mozilla Foundation", identity: BrowserTypes.FireFox,
                    versions: [{ nameTag: null, versionPrefix: null }]
                };
                list[BrowserTypes.Camino] =
                {
                    name: "Camino", vendor: "", identity: BrowserTypes.Camino,
                    versions: [{ nameTag: null, versionPrefix: null }]
                };
                list[BrowserTypes.Netscape] =
                {
                    name: "Netscape", vendor: "AOL", identity: BrowserTypes.Netscape,
                    versions: [
                        { nameTag: null, versionPrefix: null }, // for newer Netscapes (6+)
                        { nameTag: "Mozilla", versionPrefix: "Mozilla", vendor: "Netscape Communications (now AOL)" } // for older Netscapes (4-)
                    ]
                };
                list[BrowserTypes.IE] =
                {
                    name: "Internet Explorer", vendor: "Microsoft", identity: BrowserTypes.IE,
                    versions: [{ nameTag: "MSIE", versionPrefix: "MSIE " }]
                };
                // ... connect the parents and return the static list ...
                for (var i = list.length - 1; i >= 0; --i)
                    if (list[i] && list[i].versions)
                        for (var i2 = list[i].versions.length - 1; i2 >= 0; --i2)
                            if (list[i].versions[i2])
                                list[i].versions[i2].parent = list[i];
                return list;
            })();

            var __osList: OSInfo[] = [
                {
                    name: "iPhone",
                    identity: OperatingSystems.iOS
                },
                {
                    name: "Linux",
                    identity: OperatingSystems.Linux
                },
                {
                    name: "Win",
                    identity: OperatingSystems.Windows
                },
                {
                    name: "Mac",
                    identity: OperatingSystems.Mac
                }
            ];

            /** Holds a reference to the agent data detected regarding browser name and versions. */
            export var browserVersionInfo: BrowserAgentVersionInfo = null;

            /** Holds a reference to the agent data detected regarding the host operating system. */
            export var osInfo: OSInfo = null;

            var __findBrowser = (): BrowserAgentVersionInfo => {
                var agent = navigator.vendor + "," + navigator.userAgent, bInfo: BrowserInfo, version: BrowserAgentVersionInfo, versionPrefix: string;
                for (var i = 0, n = __browserList.length; i < n; ++i) {
                    bInfo = __browserList[i];
                    if (bInfo)
                        for (var i2 = 0, n2 = bInfo.versions.length; i2 < n2; ++i2) {
                            version = bInfo.versions[i2];
                            versionPrefix = version.versionPrefix || (bInfo.name + '/');
                            if (version && agent.indexOf(version.nameTag || bInfo.name) != -1 && agent.indexOf(versionPrefix) != -1)
                                return version;
                        }
                }
                return null;
            }

            var __findOS = (): OSInfo => {
                var osStrData = navigator.platform || navigator.userAgent;
                for (var i = 0, n = __osList.length; i < n; ++i)
                    if (osStrData.indexOf(__osList[i].name) != -1)
                        return __osList[i];
            }

            var __detectVersion = (versionInfo: BrowserAgentVersionInfo): number=> {
                var versionStr = navigator.userAgent + " / " + navigator.appVersion;
                var versionPrefix = versionInfo.versionPrefix || (versionInfo.parent.name + "/");
                var index = versionStr.indexOf(versionPrefix);
                if (index == -1) return -1;
                return parseFloat(versionStr.substring(index + versionPrefix.length));
            }

            /** The name of the detected browser. */
            export var name: string = "";

            /** The browser's vendor. */
            export var vendor: string = "";

            /** The operating system detected. */
            export var os: OperatingSystems = OperatingSystems.Unknown;

            /** The browser version detected. */
            export var version: number = -1;

            /** The type of browser detected. */
            export var type: BrowserTypes = ((): BrowserTypes => {
                var browserType: BrowserTypes = BrowserTypes.Unknown, browserInfo: BrowserInfo;
                if (host.isClient()) {
                    if (!browserVersionInfo) browserVersionInfo = __findBrowser();
                    browserInfo = browserVersionInfo.parent;
                    osInfo = __findOS();
                    browserType = browserInfo.identity;
                    name = browserInfo.name;
                    vendor = browserVersionInfo.vendor || browserVersionInfo.parent.vendor;
                    browserType = browserInfo != null ? browserInfo.identity : BrowserTypes.Unknown;
                    version = __detectVersion(browserVersionInfo);
                    os = osInfo != null ? osInfo.identity : OperatingSystems.Unknown;
                }
                else browserType = BrowserTypes.Server;
                return browserType;
            })();
        }

        // -------------------------------------------------------------------------------------------------------------------
    }

    // =======================================================================================================================

    /** A reference to plugins included in the project. */
    export module Scripts {

        /** Used to strip out the plugin header */
        export var PLUGIN_USING_HEADER_REGEX = /^(\s*\n)*\s*module\s*DreamSpace.Plugins\s*{(?:[^}].*\n)*}[\r\n]/;

        /** Determines the requested load order of the script. This is only applicable in client side browser environments. */
        export enum ScriptPlacement { // Note: This order is important, and goes from first to last based on page load progression.
            /** High priority: The script should be the first thing loaded in the header section (before the body). */
            HeaderTop,
            /** Normal priority: The script should be the last thing loaded in the header section (before the body). */
            HeaderBottom,
            /** Low priority: The script should be the last thing loaded within the page body. */
            BodyBottom
        }

        export enum ScriptLoadStatus {
            /** The script was requested, but couldn't be loaded. */
            Error = -1,
            /** The script is not needed. */
            NotNeeded = 0,
            /** The script was requested, but loading has not yet started. */
            Requested,
            /** The script is waiting on dependents before loading. */
            Waiting,
            /** The script is loading. */
            InProgress,
            /** The script is now available, but has not executed yet. Scripts only execute when needed (see 'DreamSpace.using'). */
            Loaded,
            /** The script has been executed. */
            Ready
        }

        export interface PluginLoadedHandler { (plugin: Plugin.$Type, $e?: EventDispatcher.$Type<Plugin.$Type, EventHandler>): boolean }
        export interface PluginReadyHandler { (plugin: Plugin.$Type, $e?: EventDispatcher.$Type<Plugin.$Type, EventHandler>): void }
        export interface PluginErrorHandler { (plugin: Plugin.$Type, $e?: EventDispatcher.$Type<Plugin.$Type, EventHandler>): void }

        /** Returns a new basic object instance. */
        export function Plugin(fullname: string, path: string, minifiedPath?: string): Plugin.$Type { return this; }
        /** The base type for all DreamSpace objects. */
        export module Plugin {
            var __loadedScriptFiles: { [index: string]: $Type; } = {};

            export class $Type extends Object.$Type {
                __timeoutHandle: number = void 0;

                /** Triggered when the script loads, but before it is ready to be used. */
                onLoaded = EventDispatcher<Plugin.$Type, PluginLoadedHandler>(this, "loaded", true, (event, handler) => {
                    event.owner.customWait = !handler.invoke(event.owner); // (note: this will return false for scripts that, though loaded ok, require delayed handling before proceeding [see 'DreamSpace.Modules.Google_Maps' for an example])
                });

                /** Triggered once the script loads AND is ready to be used. */
                onReady = EventDispatcher<Plugin.$Type, PluginReadyHandler>(this, "ready", true);

                /** Triggered if an error occurs trying to load the underlying script. */
                onError = EventDispatcher<Plugin.$Type, PluginErrorHandler>(this, "error", true);

                loadStatus: ScriptLoadStatus = ScriptLoadStatus.NotNeeded; // (-1 = error, 0 = don't load, 1 = request to load, 2 = load in progress, and 3 = load completed)
                loadError: string = null;
                required: boolean = false; // (true if the script is required - the application will fail to execute if this occurs, and an exception will be thrown)
                scriptPlacement: ScriptPlacement = ScriptPlacement.BodyBottom;

                /** A list of modules dependent on this module. */
                dependants: $Type[] = null;
                /** A list of modules that this module is dependent on. */
                dependents: $Type[] = null;

                isInclude() { return this.path && this.fullname == this.path; }

                errorMessage: string = null;
                loadingTimedOut: boolean = false;

                /** If true, then the module is waiting to complete based on some outside custom script/event. */
                customWait: boolean = false;

                constructor(public fullname: string, public path: string, public minifiedPath: string = null) {
                    super();
                }

                toString() { return this.fullname; }
                toValue() { return this.fullname; }

                __tryLaunchingApp() {
                    var log = System.Diagnostics.log("ModuleInfo.__tryLaunchingApp()", "Calling 'beginModuleDownloads()' to check for more modules (scripts) ...").beginCapture();
                    // ... check if more scripts were just added/flagged for downloading ...

                    beginModuleDownloads();

                    // ... all REQUIRED scripts downloaded ok? then continue to launch the app ...

                    if (allPluginsLoaded()) {
                        if (!$DSLoader.modulesLoaded)
                            $DSLoader.doOnAllModulesLoaded();
                        else
                            log.write("No more modules to load.", System.Diagnostics.LogTypes.Success);
                    } else
                        log.write("More modules still pending.");

                    log.endCapture();
                }

                doLoadingCompleted() {
                    if (this.__timeoutHandle != void 0) {
                        clearTimeout(this.__timeoutHandle);
                        this.__timeoutHandle = void 0;
                    }

                    var log = System.Diagnostics.log("ModuleInfo.doLoadingCompleted()", "Loading completed for module '" + this.fullname + "'.").beginCapture();

                    if (this.customWait || this.loadStatus >= ScriptLoadStatus.Requested && this.loadStatus < ScriptLoadStatus.Loaded
                        || this.loadStatus == ScriptLoadStatus.Error && this.loadingTimedOut) { // (note: a script that was *timed out* can suddenly become loaded successfully due to network lag)

                        if (this.customWait)
                            log.write("Request to continue executing events on custom waiting module '" + this.fullname + "'...");
                        else
                            log.write("Module '" + this.fullname + "' has completed loading.", System.Diagnostics.LogTypes.Success);

                        this.loadStatus = ScriptLoadStatus.Loaded;
                        this.loadingTimedOut = false;
                        this.customWait = false; // (to be set true by 'onloaded' handlers to differ the 'onready' event)

                        // ... call script loaded events ...
                        // (note: this may cause 'this.customWait' to become true, which will differ the 'ready' event until later)

                        if (this.onLoaded.hasHandlers()) {
                            log.write("Triggering 'loaded' event for module '" + this.fullname + "' ...");
                            this.onLoaded.autoTrigger = true;
                            this.onLoaded.dispatch(this);
                            log.write("All 'loaded' handlers called for module '" + this.fullname + "'.");
                        }

                        if (!this.customWait) {
                            // ... no waiting required; trigger 'ready' event, then try to run the app if all modules are loaded ...

                            if (this.onReady.hasHandlers()) {
                                log.write("Triggering 'ready' event for module '" + this.fullname + "' ...");
                                this.onReady.autoTrigger = true;
                                this.onReady.dispatch(this);
                                log.write("All 'ready' handlers called for module '" + this.fullname + "'.");
                            }

                            this.__tryLaunchingApp(); // (try launching after this event)
                        }
                    }

                    log.endCapture();
                }

                doLoadingError(errorMessage: string = null) {
                    if (this.__timeoutHandle != void 0) {
                        clearTimeout(this.__timeoutHandle);
                        this.__timeoutHandle = void 0;
                    }

                    if (this.loadStatus >= ScriptLoadStatus.Requested) {
                        this.loadStatus = ScriptLoadStatus.Error;
                        this.errorMessage = "The module '" + this.fullname + "' (" + this.path + ") has failed to load." + (errorMessage ? " \r\nReason: " + errorMessage : "");
                        if (this.required)
                            throw System.Exception.from(this.errorMessage, this); // (note: this will automatically log an error)
                        else
                            System.Diagnostics.log("ModuleInfo.doLoadingError()", this.errorMessage, System.Diagnostics.LogTypes.Warning);
                    }

                    this.__tryLaunchingApp(); // (try launching after this event)
                }

                tryLoading(): boolean { // (note: when dependencies are setup, a callback is added to each one to call this function)
                    // ... check if all dependent modules are loaded, then attempt to load this one ...
                    var i, n, dep: Plugin.$Type, ready = true;

                    var log = System.Diagnostics.log("ModuleInfo.tryLoading()", "* Trying to load module '" + this.fullname + "' ... ", System.Diagnostics.LogTypes.Info).beginCapture();

                    if (this.loadStatus != ScriptLoadStatus.Requested && this.loadStatus != ScriptLoadStatus.Waiting) {
                        log.write("Unable to continue request - can only call 'tryLoading()' on modules that are in either in 'Requested' or 'Waiting' status.");
                        return; // (this script is either not needed, is waiting on other scripts, loading is in progress, was completed, or is in error)
                    }

                    if (this.dependants && this.dependants.length) {
                        log.write("Module '" + this.fullname + "' has " + this.dependants.length + " other dependent modules.");

                        for (i = 0, n = this.dependants.length; i < n; ++i) {
                            dep = this.dependants[i];

                            if (dep.loadStatus == ScriptLoadStatus.Loaded) {
                                log.write(i + ": Dependent module '" + dep.fullname + "' is loaded ok.");
                                continue; // (all good here, move along ;) )
                            }
                            else if (dep.loadStatus == ScriptLoadStatus.NotNeeded) {
                                log.write(i + ": Dependent module '" + dep.fullname + "' is required - changing status from 'NotNeeded' to 'Requested'.");
                                dep.loadStatus = ScriptLoadStatus.Requested; // (the module is required implicitly by this module)
                                // ... double check to make the placement is correct ...
                                if (dep.scriptPlacement >= this.scriptPlacement) { // (lower is higher priority - scripts move up in priority as requested, never down)
                                    if (this.scriptPlacement == ScriptPlacement.HeaderTop) {
                                        this.scriptPlacement = ScriptPlacement.HeaderBottom;
                                        dep.scriptPlacement = ScriptPlacement.HeaderBottom;  // (adding to the bottom of a tag group is better for dependent scripts)
                                    }
                                    else dep.scriptPlacement = this.scriptPlacement - 1; // (move placement for dependent up -1 [upwards] from current script [note: this currently only moves from 'BodyBottom' to 'HeaderBottom'])
                                }
                            }

                            if (dep.loadStatus == ScriptLoadStatus.Requested || dep.loadStatus == ScriptLoadStatus.Waiting || dep.loadStatus == ScriptLoadStatus.InProgress
                                || dep.loadStatus == ScriptLoadStatus.Error && dep.required) {

                                if (this.loadStatus == ScriptLoadStatus.Requested) {

                                    // ... this module is in first time request status, so add the callbacks on this pass ...
                                    dep.onReady.addListener((plugin) => {
                                        this.tryLoading();
                                        return true;
                                    });

                                    if (!this.dependents)
                                        this.dependents = [];
                                    this.dependents.push(dep);

                                    log.write(i + ": Hooked into dependent module '" + dep.fullname + "' to be notified when it completes loading.");

                                    if (dep.loadStatus == ScriptLoadStatus.Requested) {
                                        log.write(i + ": Dependent module '" + dep.fullname + "' is not yet loading; attempting to load it now ...");
                                        dep.tryLoading(); // (loading not yet initiated, attempt to initiate loading now)
                                    }
                                }

                                ready = false; // (fail if loading is still in progress, or there was an error on a *required* module)
                            }
                            else log.write(i + ": Still waiting on dependent module '" + dep.fullname + "' (status: " + ScriptLoadStatus[dep.loadStatus] + ") ...");
                        }
                    }

                    if (ready) {
                        this.loadStatus = ScriptLoadStatus.Requested;
                        this.__loadScript(); // (all dependent modules loaded, so go ahead and try to load this one next)
                    }
                    else {
                        log.write("Module '" + this.fullname + "' is " + (this.loadStatus == ScriptLoadStatus.Waiting ? "still" : "") + " waiting for dependent modules to complete loading.");
                        this.loadStatus = ScriptLoadStatus.Waiting;
                    }

                    log.endCapture();

                    return ready;
                }

                __loadScript() { // (note: at this point it should be safe to assume the DOM is fully parsed [though content may still be downloading])
                    if (this.loadStatus != ScriptLoadStatus.Requested)
                        return; // (this script is either not needed, is waiting on other scripts, loading is in progress, was completed, or is in error)

                    var src = System.Diagnostics.debug // (note: anything other than 0 is a debug mode)
                        ? this.path : this.minifiedPath || this.path; // (try to load the minified script; fallback to the normal path if there's no minified script path)

                    var log = System.Diagnostics.log("ModuleInfo.__loadScript()", "Loading module '" + this.fullname + "' ('" + src + "') ...", System.Diagnostics.LogTypes.Info).beginCapture();

                    // ... first check if another script file with the same path was already loaded, is is being loaded ...
                    var alreadyLoadedPlugin = __loadedScriptFiles[this.path];
                    if (alreadyLoadedPlugin) {
                        if (alreadyLoadedPlugin.loadStatus == ScriptLoadStatus.Loaded) {
                            log.write("Module '" + this.fullname + "' is already loaded.");
                            this.doLoadingCompleted(); // (script with same path already loaded ok)
                        } else {
                            log.write("Module '" + this.fullname + "' is still loading.");
                            alreadyLoadedPlugin.onReady.addListener((plugin) => { this.doLoadingCompleted(); return true; }); // (wait on the matching script to load, then complete this one also!)
                        }
                        log.endCapture();
                        return;
                    }

                    this.loadStatus = ScriptLoadStatus.InProgress; // (note: this is done first in case script is executed immediately upon loading the JS file [in case something tries to call this function again])
                    this.errorMessage = null; // (in case of a new load attempt)
                    this.loadingTimedOut = false;

                    if (host.isClient()) {
                        var newScript: HTMLScriptElement = document.createElement('script'), targetElement: HTMLElement;

                        switch (this.scriptPlacement) {
                            case ScriptPlacement.HeaderTop:
                            case ScriptPlacement.HeaderBottom:
                                targetElement = document.head;
                                if (!targetElement)
                                    throw System.Exception.from("Unable to locate the head tag.", this);
                                break;
                            case ScriptPlacement.BodyBottom:
                                targetElement = document.body;
                                if (!targetElement)
                                    throw System.Exception.from("Unable to locate the body tag.", this);
                                break;
                        }


                        var plugin = this;
                        newScript.onload = newScript.onreadystatechange = function () {
                            if (!this.readyState || // (readyState is only valid if 'onreadystatechange' is supported, otherwise 'onload' was triggered [but remember that IE9+ supports both!])
                                this.readyState === "loaded" || this.readyState === "complete") {
                                plugin.doLoadingCompleted();
                                // Handle memory leak in IE
                                newScript.onload = newScript.onreadystatechange = null;
                                //??if (targetElement && targetElement.parentNode) {
                                //    targetElement.parentNode.removeChild(script);
                                //}
                            }
                        };
                        newScript.onerror = (ev: ErrorEvent) => {
                            plugin.doLoadingError("Either your network connection is down, the file is missing, or there was a problem executing the script.");
                        };
                        this.__timeoutHandle = setTimeout(() => {
                            plugin.loadingTimedOut = true;
                            plugin.doLoadingError("Script loading timed out - try refreshing the browser in case of network lag.");
                        }, 20000); // (a timeout to check, just in case)
                        // TODO: The events needs to be tested in Safari and IE<=9
                        // TODO: Look into a timer to re-try the loading for timed out scripts (or at least to auto resolve if the script DOES actually get loaded).

                        //// (recommended pattern for script load detection: http://bit.ly/18gsqtw)
                        //if (newScript.addEventListener)
                        //    newScript.addEventListener("load", () => { plugin.doLoadingCompleted((<any>newScript).constructor); }, false);
                        //else if (newScript.readyState)
                        //    newScript.onreadystatechange = () => {
                        //        if (newScript.readyState === 'loaded' || newScript.readyState === 'complete') {
                        //            newScript.onreadystatechange = null;

                        //        }
                        //    };
                        //else setTimeout(() => { plugin.doLoadingCompleted((<any>newScript).constructor); }, 60000); // (should never happen, but perhaps better than throwing an error)
                        // TODO: This needs to be tested in Safari.

                        __loadedScriptFiles[this.path] = this;

                        newScript.src = src;

                        switch (this.scriptPlacement) {
                            case ScriptPlacement.HeaderTop: targetElement.insertBefore(newScript, targetElement.firstChild); break;
                            case ScriptPlacement.HeaderBottom:
                            case ScriptPlacement.BodyBottom: targetElement.appendChild(newScript); break;
                        }
                    }

                    log.endCapture();
                }
            }

            AppDomain.registerClass(Plugin, [DreamSpace, System, Modules]);
        }

        /** Returns a new module object (only - does not load it). */
        export function Module(fullname: string, path: string, minifiedPath?: string): Plugin.$Type { return this; }
        /** Contains static module properties and functions. */
        export module Module {
            var __loadedScriptFiles: { [index: string]: $Type; } = {};

            export class $Type extends Object.$Type {
                constructor(public fullname: string, public path: string, public minifiedPath: string = null) {
                    super();
                }
            }

            AppDomain.registerClass(Plugin, [DreamSpace, System, Modules]);
        }


        export var ___plugins: { [fullname: string]: Plugin.$Type } = {};

        /** This is the path to the root of the DreamSpace JavaScript files ('DreamSpaceJS/' by default).
        * Note: This should either be empty, or always end with a URL path separator ('/') character (but the system will assume to add one anyhow if missing). */
        export var pluginFilesBasePath: string = "DreamSpaceJS/";

        /** Translates a module relative or full type name to the actual type name (i.e. '.ABC' to 'DreamSpace.ABC', or 'System'/'System.' to 'DreamSpace'/'DreamSpace.'). */
        export function translateModuleTypeName(moduleFullTypeName: string) {
            if (moduleFullTypeName.charAt(0) == '.')
                moduleFullTypeName = "DreamSpace" + moduleFullTypeName; // (just a shortcut to reduce repetition of "DreamSpace." at the start of full module type names during registration)
            else if (moduleFullTypeName == "System" || moduleFullTypeName.substr(0, "System.".length) == "System.")
                moduleFullTypeName = "DreamSpace" + moduleFullTypeName.substr("System".length); // ("System." maps to "DreamSpace." internally to prevent compatibility issues)
            return moduleFullTypeName;
        };

        function __setDependencies(plugin: Plugin.$Type, dependencies: Plugin.$Type[]) {
            var i, dep: Plugin.$Type, i2, n, n2;

            if (!plugin.dependants)
                plugin.dependants = []; // (make sure an array exists first)

            // ... add only missing dependencies ...

            if (dependencies && dependencies.length > 0) {
                for (i = 0, n = dependencies.length; i < n; ++i) {
                    dep = dependencies[i];

                    if (dep === void 0 || typeof dep === 'object' && !(dep instanceof Plugin))
                        throw System.Exception.from("The specified dependency is invalid - a 'ModuleInfo' object was expected.", plugin);

                    for (i2 = 0, n2 = plugin.dependants.length; i2 < n2; ++i2)
                        if (plugin.dependants[i2].fullname == dep.fullname) continue; // (exists, so skip)

                    plugin.dependants.push(dep);
                }
            }
        }

        /** Parses the text (usually a file name/path) and returns the non-minified and minified versions in an array (in that order).
          * If no tokens are found, the second item in the array will be null.
          * The format of the tokens is '{min:[non-minified-text|]minified-text}', where '[...]' is optional (square brackets not included).
          */
        export function processMinifyTokens(text: string): string[] {
            var tokenRegEx = /{min:[^\|}]*?\|?[^}]*?}/gi;
            var minTokens = text.match(tokenRegEx); // (note: more than one is supported)
            var minifiedText: string = null;
            var token: string, minParts: string[];

            if (minTokens) { // (if tokens were found ...)
                minifiedText = text;

                for (var i = 0, n = minTokens.length; i < n; ++i) {
                    token = minTokens[i];
                    minParts = token.substring(5, token.length - 1).split('|');
                    if (minParts.length == 1) minParts.unshift("");
                    minifiedText = minifiedText.replace(token, minParts[1]);
                    text = text.replace(token, minParts[0]); // (delete the token(s))
                }
            }

            return [text, minifiedText];
        }

        export interface IUsingModule {
            /** Checks to see if the plugin script is already applied, and executes it if not.
            */
            (): typeof Modules;
            /** The plugin object instance details. */
            module: Plugin.$Type;
            then: (success: { (mod: Plugin.$Type, response: any): any }, error?: { (mod: Plugin.$Type): any }) => IUsingModule;
            catch: (onerror: { (mod: Plugin.$Type): any }) => IUsingModule;
        }

        /** This is usually called from the 'DreamSpace.[ts|js]' file to register script files (plugins), making them available to the application based on module names (instead of file names).
        * When 'DreamSpace.using.{...someplugin}()' is called, the required script files are then executed as needed.
        * This function returns a function that, when called, will execute the loaded script.  The returned object also as chainable methods for success and error callbacks.
        * @param {string} moduleFullTypeName The full type name of the module, such as 'DreamSpace.UI', or 'jquery'.
        * You can also use the '{min:[non-minified-text|]minified-text}' token sequence (where '[...]' is optional, square brackets not included) to define the minified and non-minified text parts.
        * @param {string} moduleFileBasePath (optional) The path to the '.js' file, including the filename + extension.  If '.js' is not found at the end, then
        * the full module type name is appended, along with '.js'. This parameter will default to 'DreamSpace.moduleFilesBasePath'
        * (which is 'DreamSpaceJS/' by default) if null is passed, so pass an empty string if this is not desired.
        * You can also use the '{min:[non-minified-text|]minified-text}' token sequence (where '[...]' is optional, square brackets not included) to define the minified and non-minified text parts.
        * @param {Function} onLoadedCallback (optional) Called once the module/script has loaded and executed.  This is useful if preparation is needed after a script
        * loads, but before it gets used by dependants.  If used, return true to signal success, and false to delay success until a custom script decides later by
        * calling '{ModuleInfo}.doLoadingCompleted()' - any dependant scripts will not continue to load until that function is called.
        * @param {ModuleInfo[]} dependencies A list of modules that this module depends on.
        */
        export function module(dependencies: IUsingModule[], pluginFullTypeName: string, pluginFileBasePath: string = null): IUsingModule {

            if (!pluginFullTypeName)
                throw System.Exception.from("A full type name path is expected.");

            var Path = System.IO.Path;

            // ... extract the minify-name tokens and create the proper names and paths for both min and non-min versions ...

            var results = processMinifyTokens(pluginFullTypeName);
            var minifiedFullTypeName = null;
            if (results[1]) {
                pluginFullTypeName = translateModuleTypeName(results[0]);
                minifiedFullTypeName = translateModuleTypeName(results[1]);
            }
            else pluginFullTypeName = translateModuleTypeName(pluginFullTypeName); // (translate "System." to "DreamSpace." and "." to "DreamSpace." internally)

            // ... if the JavaScript file is not given, then create the relative path to it given the full type name ...

            var path: string = pluginFileBasePath != null ? ("" + pluginFileBasePath).trim() : pluginFilesBasePath;
            var minPath: string = null;

            if (path && path.charAt(0) == '~')
                path = Path.combineURLPaths(pluginFilesBasePath, path.substring(1)); // ('~' is a request to insert the current default path; eg. "~DreamSpace.System.js" for "DreamSpaceJS/DreamSpace.System.js")

            results = processMinifyTokens(path);
            if (results[1]) {
                path = results[0];
                minPath = results[1];
            }
            else if (minifiedFullTypeName) minPath = path;

            if (!Path.hasFileExt(path, '.js')) { //&& !/^https?:\/\//.test(path)
                // ... JavaScript filename extension not found, so add it under the assumed name ...
                if (!path || path.charAt(path.length - 1) == '/')
                    path = Path.combineURLPaths(path, pluginFullTypeName + ".js");
                else
                    path += ".js";
            }
            if (minPath && !Path.hasFileExt(minPath, '.js')) { //&& !/^https?:\/\//.test(path)
                // ... JavaScript filename extension not found, so add it under the assumed name ...
                if (!minPath || minPath.charAt(minPath.length - 1) == '/')
                    minPath = Path.combineURLPaths(minPath, minifiedFullTypeName + ".js");
                else
                    minPath += ".js";
            }

            // ... add the module if it doesn't already exist, otherwise update it ...

            var plugin = ___plugins[pluginFullTypeName];

            if (plugin === void 0)
                ___plugins[pluginFullTypeName] = plugin = Plugin(pluginFullTypeName, path, minPath); // (note: this can only be changed if the type hasn't been loaded yet)
            else {
                plugin.fullname = pluginFullTypeName; // (just in case it changed somehow - this must always match the named index)
                if (!plugin.path)
                    plugin.path = path; // (update path only if missing)
                if (!plugin.minifiedPath)
                    plugin.minifiedPath = minPath; // (update minified-path only if missing)
            }

            if (dependencies && dependencies.length > 0)
                __setDependencies(plugin, dependencies);

            if (onLoadedCallback)
                plugin.onLoaded.addListener(onLoadedCallback);

            var usingPluginFunc: IUsingModule = <IUsingModule><any>(() => {
                // ... request to apply the plugin code (ignored if already loaded) ...
                switch (plugin.loadStatus) {
                    case ScriptLoadStatus.Ready: return;
                    case ScriptLoadStatus.Loaded: {
                        // ... execute the script ...
                        plugin.applyScript();
                        plugin.loadStatus = ScriptLoadStatus.Ready;
                        break;
                    }
                    case ScriptLoadStatus.Error: throw DreamSpace.System.Exception.from("The plugin '" + plugin.fullname + "' cannot be executed as it is in an error state.", plugin);
                    case ScriptLoadStatus.Requested:
                    case ScriptLoadStatus.InProgress:
                    case ScriptLoadStatus.Waiting:
                        throw DreamSpace.System.Exception.from("The plugin '" + plugin.fullname + "' cannot be executed as loading is not yet completed.", plugin);
                    case ScriptLoadStatus.NotNeeded: {
                        // ... begin loading ...
                    }
                }
            });

            usingPluginFunc.module = plugin;

            usingPluginFunc.then = (success: { (plugin: Plugin.$Type, response: any): any }, error: { (plugin: Plugin.$Type): any }) => void {
            };

            usingPluginFunc.catch = (onerror: { (plugin: Plugin.$Type): any }) => void {
            };


            return usingPluginFunc;
        };

        /** Returns true if all modules are loaded and ready for use. 
        */
        export function allPluginsLoaded(): boolean {
            for (var p in ___plugins) {
                var plugin = ___plugins[p];

                if (plugin.loadStatus != ScriptLoadStatus.NotNeeded && plugin.loadStatus != ScriptLoadStatus.Loaded &&
                    (plugin.loadStatus != ScriptLoadStatus.Error || plugin.required)) {
                    System.Diagnostics.log("allPluginsLoaded()", "Plugin '" + plugin.fullname + "' is not yet loaded (status=" + ScriptLoadStatus[plugin.loadStatus] + ").");
                    return false;
                }

                //??if (!plugin.moduleType && !plugin.isInclude() && plugin.required)
                //    return false; // (check for missing module type reference on required modules [though, if the previous line passes, this will likely pass also])
            }
            return true;
        };

        /** Triggered immediately upon the DOM being ready; though not necessarily before all content (eg. images) have loaded.
        * All scripts request by calling the "DreamSpace.using()" function, and without any dependents, will begin to load in parallel.  As each required script
        * loads, it notifies it's dependants to check if all other required scripts are loaded.  If all dependents are loaded, then the dependant script itself
        * attempts to load next.
        */
        export function beginModuleDownloads() {
            var typeName, plugin: Plugin.$Type;
            for (typeName in ___plugins) {
                plugin = ___plugins[typeName];
                if (plugin.loadStatus == ScriptLoadStatus.Requested) // (usually this is set via a call to "using()" by the user)
                    plugin.tryLoading(); // (attempt to load this; it may cascade the request if dependent on other scripts)
            }
            //??if (host.isClient())
            //    setTimeout(() => { if (allModulesLoaded()) beginScriptDownloads(); }, 500); // (keep loading modules until there are no more requests)
        }

        /** End users must call this to make sure a required module gets loaded.  
          * The 'required' parameter is 'true' by default to make sure the module is loaded before the application runs, but if you don't NEED a module, set this to 'false'.
          * 'onLoadedCallback' is called once the module is loaded and ready.
          * Warning: After all modules are loaded, calling this function without an 'onLoadedCallback' value will only check if a module exists, and throw an error
          * if missing.  To load scripts on demand, a callback must be specified for 'onLoadedCallback'. If no callback is needed, then an explicit 'null' value
          * can be used instead.
          */
        export function using(plugin: Plugin.$Type, required?: boolean, onReady?: PluginLoadedHandler, onError?: PluginErrorHandler, scriptPlacement?: ScriptPlacement): Plugin.$Type;
        /** 
         * End users must call this to make sure a series of required modules get loaded (each module is loaded in parallel).  
         * The 'required' parameter is 'true' by default to make sure the modules are loaded before the application runs, but if you don't NEED any of the modules, set this to 'false'.
         * 'onLoadedCallback' is called once ALL the modules are loaded and ready.
         * TIP: To load scripts in series, use multiple nested 'using()' calls within callbacks.
         */
        export function using(plugin: Plugin.$Type[], required?: boolean, onReady?: PluginLoadedHandler, onError?: PluginErrorHandler, scriptPlacement?: ScriptPlacement): Plugin.$Type;
        export function using(plugin: any, required: boolean = true, onReady?: PluginLoadedHandler, onError?: PluginErrorHandler, scriptPlacement: ScriptPlacement = ScriptPlacement.BodyBottom): Plugin.$Type {
            // TODO: Allow delay-loading scripts for when the user is idle.

            if (plugin instanceof Array) {

                // ... build list of modules that are not yet loaded ...

                var modulesRequested: Plugin.$Type[] = <Plugin.$Type[]>plugin;
                var moduleLoadCount: number = 0;
                var moduleErrorCount: number = 0;

                for (var i = 0, n = modulesRequested.length; i < n; ++i)
                    using(plugin[i], required, (plugin) => {
                        if (++moduleLoadCount == modulesRequested.length && onReady)
                            onReady(plugin); // (all required modules loaded; execute callback)
                        else if (moduleErrorCount + moduleLoadCount == modulesRequested.length && onError)
                            onError(plugin);
                        return true;
                    }, (plugin) => {
                            if (++moduleErrorCount + moduleLoadCount == modulesRequested.length && onError)
                                onError(plugin);
                        }, scriptPlacement);

                return null;
            }

            var _plugin: Plugin.$Type = plugin;

            var log = System.Diagnostics.log("DreamSpace.using()", "Request to use module '" + _plugin.fullname + "'.", System.Diagnostics.LogTypes.Info).beginCapture();

            if (_plugin === void 0)
                throw System.Exception.from("The specified module doesn't exist yet (is not registered).  You may need to update your 'manifest.js' file to include it.  Modules are registered by calling 'DreamSpace.Modules.Module_Full_Type_Name = DreamSpace.registerModule('Module.Full.Type.Name', /*optional*/'PathTo/Module.Full.Type.Name.js');'", plugin);

            if (_plugin.loadStatus == ScriptLoadStatus.NotNeeded)
                _plugin.loadStatus = ScriptLoadStatus.Requested;

            if (required) _plugin.required = true; // (note: once 'required ' is true, it should never become false [since it means it's required by user code somewhere])

            if (scriptPlacement < _plugin.scriptPlacement)
                _plugin.scriptPlacement = scriptPlacement; // (lower is higher priority - scripts move up in priority as requested, never down)

            if (onReady !== void 0 || !$DSLoader.manifestLoaded) {
                // ... a callback exists, so it is safe to make the load request and return ...

                if (onReady)
                    _plugin.onReady.addListener(onReady); // (if already loaded, the callback will execute immediately)

                if ($DSLoader.modulesLoaded && (_plugin.loadStatus == ScriptLoadStatus.Requested || _plugin.loadStatus == ScriptLoadStatus.Waiting))
                    _plugin.tryLoading(); // (make immediate load request if module load sequence is already completed)
            }
            else {
                // ... no callback was given, so this type MUST exist NOW (EXCEPT: before the manifest is loaded) ...

                if (_plugin.loadStatus != ScriptLoadStatus.Loaded)
                    throw System.Exception.from("Module '" + _plugin.fullname + "' is registered, but not yet loaded.  Make sure the correct dependencies are given during module registration in your manifest file.");

                if (onReady)
                    _plugin.onReady.addListener(onReady); // (if already loaded, the callback will execute immediately)
            }

            log.endCapture();

            return _plugin;
        };

        /** Loads the requested JavaScript file. The application only starts when all required modules and include files have loaded.
        * The 'onLoadedCallback' function will only be called when the script load completes successfully.
        */
        export function include(jsFile: string, required: boolean = true, onReady: PluginLoadedHandler = null, scriptPlacement: ScriptPlacement = ScriptPlacement.BodyBottom): Plugin.$Type {

            if (!jsFile)
                throw System.Exception.from("No file was specified to include.");

            if (jsFile.charAt(0) == '~')
                jsFile = Path.combineURLPaths(pluginFilesBasePath, jsFile.substring(1)); // ('~' is a request to insert the current default path; eg. "~DreamSpace.System.js")

            // ... add the extension if missing ...
            if (!Path.hasFileExt(jsFile, '.js'))
                jsFile += ".js";

            var log = System.Diagnostics.log("DreamSpace.using()", "Request to include file '" + jsFile + "'.", System.Diagnostics.LogTypes.Info).beginCapture();

            var plugin = ___plugins[jsFile];

            if (plugin === void 0)
                ___plugins[jsFile] = plugin = Plugin(jsFile, jsFile); // (first time script is requested)
            // (note: the name is the path+filename, as it is possible to get script resource WITHOUT a .js filename [simply based on mime type alone])

            plugin.loadStatus = ScriptLoadStatus.Requested;

            if (required) plugin.required = true; // (note: once 'required ' is true, it should never become false [since it means it's required by user code somewhere])

            if (scriptPlacement < plugin.scriptPlacement)
                plugin.scriptPlacement = scriptPlacement; // (lower is higher priority - scripts move up in priority as requested, never down)

            if (onReady)
                plugin.onReady.addListener(onReady); // (if already loaded, the callback will execute immediately)

            //??if ($DSLoader.modulesLoaded)
            plugin.tryLoading(); // (include requests are done on the fly, and unlike registered modules, are loaded immediately upon request [asynchronously])

            log.endCapture();

            return plugin;
        };

        /**
         * Used to require the inclusion of another manifest file while loading the manifest layer. 
         */
        export var require = module;

        /** A reference to plugins included in the project. */
        export module Modules {
        }
    } // End Scripting

    /** Use to compile & execute required plugin scripts. */
    export import using = Scripts.Modules;

    // =======================================================================================================================

    /** Called when the DreamSpace system is ready for use.  The system should not be accessed until this method is called. */
    export var onReady: { (): any }[] = []; // = System.Events.EventDispatcher<any, () => any>(null, 'ready', true);

    // =======================================================================================================================

    /** Starts the application.  If false is returned, then the application is queued to run when required resources and scripts have downloaded.
    * If true is returned, then the application was either started upon return, or is already started.
    */
    export function run() { } // (this is replaced near the end of this script with the loader implementation)

    // =======================================================================================================================

    /** DreamSpace specific system related modules. */
    export module Scripts.Modules {
        // -------------------------------------------------------------------------------------------------------------------
        // System Modules 

        /** References the manifest file for the DreamSpace framework. */
        export var Manifest = require([], 'manifest{min:.min}', '~System');

        // -------------------------------------------------------------------------------------------------------------------
    }

    // =======================================================================================================================
}

// =======================================================================================================================

var dreamspace = DreamSpace; // (allow all lower case usage)
DreamSpace['__System'] = this['System']; // (stored any existing value to restore later)
DreamSpace['__using'] = this['using']; // (stored any existing value to restore later)

/** The base module for all DreamSpace framework types. */
import System = DreamSpace.System; // (try to make this global, unless otherwise predefined ...)

/** Use to compile & execute required plugin scripts. */
import using = DreamSpace.using;

// ... users will need to make sure 'System' and/or 'using' are not in use (undefined or set to null before this script is executed) in order to use it as a valid type reference ...

if (typeof DreamSpace['__System'] !== 'undefined' && DreamSpace['__System'] !== null) { // (users should reference "System.", but "DreamSpace.System" can be used if the global 'System' is needed for something else)
    this['System'] = DreamSpace['__System']; // (a valid 'System' value already exists, so restore the old value)
} else DreamSpace.__rootNamespace = "System"; // (make all full type names reference the proper root namespace)

if (typeof DreamSpace['__using'] !== 'undefined' && DreamSpace['__using'] !== null) { // (users should reference "using.", but "DreamSpace.using" can be used if the global 'using' is needed for something else)
    this['using'] = DreamSpace['__using']; // (a valid 'using' value already exists, so restore the old value)
}

// =======================================================================================================================

import $DS = DreamSpace.System;

interface IDreamSpaceLoader {
    __id: number;
    __appDomain: DreamSpace.System.AppDomain;
    domLoaded: boolean;
    onDOMLoaded: DreamSpace.System.Events.EventDispatcher.$Type<IDreamSpaceLoader, () => any>;
    pageLoaded: boolean;
    onPageLoaded: DreamSpace.System.Events.EventDispatcher.$Type<IDreamSpaceLoader, () => any>;
    manifestLoaded: boolean;
    onManifestLoaded: DreamSpace.System.Events.EventDispatcher.$Type<IDreamSpaceLoader, () => any>;
    modulesLoaded: boolean;
    onModulesLoaded: DreamSpace.System.Events.EventDispatcher.$Type<IDreamSpaceLoader, () => any>;
    runRequested: boolean;
    running: boolean;
    /** Explicit user request to queue to run when ready, regardless of debug mode.
    * Returns true if running upon return, or already running, and false if not ready and queued to run later. */
    run(): boolean;
    /** Implicit request to run only if ready, and not in debug mode. If not ready, or debug mode is set, ignore the request. (used internally) */
    runIfReady(): void;
    doOnDOMLoaded(): void;
    doOnPageLoaded(): void;
    doOnAllModulesLoaded(): void;
}

var $DSLoader = <IDreamSpaceLoader><any>DreamSpace;
$DSLoader.__id = null;
$DSLoader.__appDomain = null;
$DSLoader.domLoaded = false;
$DSLoader.onDOMLoaded = new DreamSpace.System.Events.EventDispatcher.$Type<IDreamSpaceLoader, () => any>($DSLoader, "onDOMLoaded", true);
$DSLoader.pageLoaded = false;
$DSLoader.onPageLoaded = new DreamSpace.System.Events.EventDispatcher.$Type<IDreamSpaceLoader, () => any>($DSLoader, "onPageLoaded", true);
$DSLoader.manifestLoaded = false;
$DSLoader.onManifestLoaded = new DreamSpace.System.Events.EventDispatcher.$Type<IDreamSpaceLoader, () => any>($DSLoader, "onManifestLoaded", true);
$DSLoader.modulesLoaded = false;
$DSLoader.onModulesLoaded = new DreamSpace.System.Events.EventDispatcher.$Type<IDreamSpaceLoader, () => any>($DSLoader, "onModulesLoaded", true);
$DSLoader.runRequested = false;
$DSLoader.running = false;
$DSLoader.run = function () { // (this is only available via the console or when forcibly executed and will not show in intellisense)
    var log = DreamSpace.System.Diagnostics.log("$DSLoader.run()", "Attempting to run ...").beginCapture();

    this.runRequested = true;

    if (!this.domLoaded || !this.modulesLoaded) {
        log.write("The DOM or Modules are not both loaded yet, but the request has been flagged to launch later when ready.");
        return false; // (can't run yet, but flag the request, which will skip the pause for debugging check later [if applicable])
    }

    if (this.running) {
        log.write("Already running.");
        return true;
    }

    this.running = true;

    log.write("Setting function names on modules and classes ...");
    DreamSpace.setFunctionNames(DreamSpace.global, "DreamSpace"); // (need to do this here to make sure to capture any static functions set later on 'DreamSpace')
    log.write("Done.");

    //??if ($ICE != null) {
    //    $ICE.loadLibraries(); // (load all ICE libraries after the DreamSpace system is ready, but before the ICE libraries are loaded so proper error details can be displayed if required)
    //}
    // (any errors before this point will terminate the 'onReady()' callback)
    // TODO: $ICE loads as a module, and should do this differently.

    //// ... some delay-loaded modules may be hooked into the window 'load' event, which will never fire at this point, so this has to be re-triggered ...

    //??var event = document.createEvent("Event");
    //event.initEvent('load', false, false);
    //window.dispatchEvent(event);

    // ... the system and all modules are loaded and ready ...
    log.write("Dispatching 'onReady event to begin application scripts ...", DreamSpace.System.Diagnostics.LogTypes.Info);
    DreamSpace.System.onReady.autoTrigger = true;
    DreamSpace.System.onReady.dispatchEvent();
    log.write("Loader has completed; the application is now running.", DreamSpace.System.Diagnostics.LogTypes.Success);

    log.endCapture();

    return true;
};
$DSLoader.runIfReady = function () {
    var log = DreamSpace.System.Diagnostics.log("$DSLoader.runIfReady()", "Run if ready...").beginCapture();
    if (this.domLoaded && this.pageLoaded && this.modulesLoaded
        && (this.runRequested || !DreamSpace.host.isDebugMode() && (DreamSpace.System.Diagnostics.debug != DreamSpace.System.Diagnostics.DebugModes.Debug_Wait))) {
        this.run();
    }
    else log.write("Not yet.");
    log.endCapture();
};
$DSLoader.doOnDOMLoaded = function () { // (note: executed immediately on the server before this script ends)
    if (!this.domLoaded) {
        this.domLoaded = true;
        var log = DreamSpace.System.Diagnostics.log("$DSLoader.doOnDOMLoaded()", "The DOM has loaded.", DreamSpace.System.Diagnostics.LogTypes.Success).beginCapture();
        this.onDOMLoaded.autoTrigger = true;
        this.onDOMLoaded.dispatchEvent();
        // ... now that the DOM is loaded, download the manifest script and being the module loading process ...
        // (note: once the manifest script executes, the calls to "using()"
        log.write("Loading the manifest (manifest.js) ...");
        DreamSpace.System.Modules.include('manifest', true, (plugin) => {
            this.manifestLoaded = true; // (note: 'this' compiles to '_this' and properly references the parent object to this closure)
            // ('beginScriptDownloads()' is called automatically every time a script download completes, just to make sure there's no more additional scripts
            // added.  As well, 'onAllModulesLoaded()' will be called when all requested/required scripts have downloaded successfully.)
            this.onManifestLoaded.autoTrigger = true;
            this.onManifestLoaded.dispatchEvent();
            return true;
        });
        log.endCapture();
    }
};
$DSLoader.doOnPageLoaded = function () { // (note: executed immediately on the server before this script ends)
    if (!this.pageLoaded) {
        this.doOnDOMLoaded(); // (just in case - the DOM load must precede the page load!)
        this.pageLoaded = true;
        var log = DreamSpace.System.Diagnostics.log("$DSLoader.doOnPageLoaded()", "Page loading has completed.", DreamSpace.System.Diagnostics.LogTypes.Success).beginCapture();
        this.onPageLoaded.autoTrigger = true;
        this.onPageLoaded.dispatchEvent();
        // ... now that the page is loaded, attempt to "run" the app if all modules are also loaded ...
        if (this.modulesLoaded)
            this.runIfReady();
        log.endCapture();
    }
};
$DSLoader.doOnAllModulesLoaded = function () {
    if (!this.modulesLoaded) {
        this.modulesLoaded = true;
        var log = DreamSpace.System.Diagnostics.log("$DSLoader.doOnAllModulesLoaded()", "All requested modules have been loaded.", DreamSpace.System.Diagnostics.LogTypes.Success).beginCapture();
        this.onModulesLoaded.autoTrigger = true;
        this.onModulesLoaded.dispatchEvent();
        if (this.domLoaded)
            this.runIfReady();
        log.endCapture();
    }
};

// Note: $DS is initially created with limited functionality until the system is ready!
// If on the client side, detect when the document is ready for script downloads - this will allow the UI to show quickly, and download script while the user reads the screen.
// (note: this is a two phased approach - DOM ready, then PAGE ready.
if (DreamSpace.host.isClient()) (function () {

    var readyStateTimer;

    // ... document ready events ...

    if (document.addEventListener) {
        document.addEventListener("DOMContentLoaded", () => {
            if (!$DSLoader.domLoaded) $DSLoader.doOnDOMLoaded();
        }); // (Firefox - wait until document loads)
        // (this event is fired after document and inline script loading, but before everything else loads [css, images, etc.])
    }
    else if (document.attachEvent && document.all && !(<any>window).opera) {
        // (DOM loading trick for IE8-, inspired by this article: http://www.javascriptkit.com/dhtmltutors/domready.shtml)
        document.write('<script type="text/javascript" id="domloadedtag" defer="defer" src="javascript:void(0)"><\/script>');
        document.getElementById("domloadedtag").onreadystatechange = function () {
            if (this.readyState == "complete" && !$DSLoader.domLoaded)
                $DSLoader.doOnDOMLoaded(); // (deferred script loading completes when DOM is finally read)
        }; // (WARNING: The 'complete' state callback may occur AFTER the page load event if done at the same time [has happened during a debug session])
    }
    else if (document.readyState) {

        // ... fallback to timer based polling ...
        var checkReadyState = () => {
            if (document.body)
                if (!$DSLoader.domLoaded && (document.readyState == 'loaded' || document.readyState == 'interactive')) { // (this event is fired after document and inline script loading, but before everything else loads [css, images, etc.])
                    $DSLoader.doOnDOMLoaded();
                }
                else if (!$DSLoader.pageLoaded && document.readyState == 'complete') { // (this event is fired after ALL resources have loaded on the page)
                    pageLoadReady();
                }
            if (!$DSLoader.pageLoaded && !readyStateTimer)
                readyStateTimer = setInterval(checkReadyState, 10);
        };

        checkReadyState();
    }
    //??else throw DreamSpace.exception("Unable to detect and hook into the required 'document load' events for this client browser.");
    // (NOTE: If unable to detect and hook into the required 'document load' events for this client browser, wait for the page load event instead...)

    // ... window ready events (all resources loaded) ...

    var pageLoadReady = () => { // (this event is fired after ALL resources have loaded on the page)
        if (!$DSLoader.pageLoaded) $DSLoader.doOnPageLoaded();
    };

    if (window.addEventListener)
        window.addEventListener("load", () => { pageLoadReady(); }); // (wait until whole page has loaded)
    else if (window.attachEvent)
        window.attachEvent('onload', () => { pageLoadReady(); });
    else { // (for much older browsers)
        var oldOnload = window.onload; // (backup any existing event)
        window.onload = (ev) => { // (perform a hook)
            oldOnload && oldOnload(ev);
            pageLoadReady();
        };
    }

    // ... finally, a timeout in case things are taking too long (for example, an image is holding things up if only the 'load' event is supported ...
    // (NOTE: If the user dynamically loads this script file, then the page dom/load events may not be triggered in time.)
})();
else {
    $DSLoader.doOnPageLoaded(); // (no UI to wait for, so do this now)
}

// =======================================================================================================================

//??if (typeof DreamSpaceApp === 'undefined')
//    var DreamSpaceApp = "app.js";
//??DreamSpace.using(DreamSpace.Modules.System, true, () => { // (this using is required by default)
//    DreamSpace.include(DreamSpaceApp, true, null, DreamSpace.ScriptPlacement.BodyBottom);
//});

//??declare var define: Function;

// =======================================================================================================================
